using System.Diagnostics;
using UnityEngine;
using System.Collections;
using System.Reflection;
using UnityEditor;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Threading;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets;
using System.Linq;
using cframework;

public static class AddressableTool
{
    public const string addressableInfoFileName = "info.txt";

    public class AddressableInfo
	{
		public int hotupdate_version;
		public long build_timestamp;
		public string build_time;
		public int min_app_hotupdate_version;
	}

	public static string GetRemoteBuildPath()
	{
		string remoteBuildPath = AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogBuildPath.GetValue(AddressableAssetSettingsDefaultObject.Settings);
		
		if(string.IsNullOrEmpty(remoteBuildPath))
		{
			string a = AddressableAssetSettingsDefaultObject.Settings.profileSettings.GetValueByName(AddressableAssetSettingsDefaultObject.Settings.activeProfileId, GetKeyName("Remote", "BuildPath"));
			remoteBuildPath = AddressableAssetSettingsDefaultObject.Settings.profileSettings.EvaluateString(AddressableAssetSettingsDefaultObject.Settings.activeProfileId, a);
		}

		return remoteBuildPath;
	}

	public static bool ScriptInfoEqualsOrInclude(IDictionary<string, List<KeyValuePair<string, string>>> allScriptInfoMap, IDictionary<string, List<KeyValuePair<string, string>>> assetbundlScriptInfoMap)
    {
        if (allScriptInfoMap == assetbundlScriptInfoMap)
        {
            return true;
        }

        if (allScriptInfoMap.Count < assetbundlScriptInfoMap.Count)
        {
            return false;
        }

        var e = assetbundlScriptInfoMap.GetEnumerator();
        while (e.MoveNext())
        {
            List<KeyValuePair<string, string>> fields0 = null;

            if (!allScriptInfoMap.TryGetValue(e.Current.Key, out fields0))
            {
                continue;
            }

            List<KeyValuePair<string, string>> fields1 = e.Current.Value;

            if (fields0 == null && fields1 == null)
            {
                continue;
            }

            if (fields0 == null || fields1 == null)
            {
                return false;
            }

			if (fields0.Count != fields1.Count)
			{
				return false;
			}

            for (int i = 0; i < fields1.Count; i++)
            {
                KeyValuePair<string, string> field1 = fields1[i];
                int index = fields0.FindIndex((a) =>
                    {
                        return a.Key == field1.Key && a.Value == field1.Value;
                    });
                if(index < 0)
                {
                    return false;
                }
            }
        }

        return true;
    }

	public static SortedDictionary<string, List<KeyValuePair<string, string>>> GetAddressableDependenceScriptInfos()
	{
		HashSet<string> assetPathSet = new HashSet<string>();
		List<AddressableAssetGroup> groups = AddressableAssetSettingsDefaultObject.Settings.groups;
		foreach(var group in groups)
		{
			if (group == null) continue;

			// UnityEngine.Debug.LogError(group.name);
			if(group.HasSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.PlayerDataGroupSchema>())
			{
				continue;
			}

			var entries = group.entries;
			foreach(var entry in entries)
			{
				assetPathSet.Add(entry.AssetPath);
			}
		}

		string[] assetPaths = assetPathSet.ToArray();
		string[] dependences = AssetDatabase.GetDependencies(assetPaths, true);

		HashSet<string> scriptPathSet = new HashSet<string>();
		foreach(var p in dependences)
		{
			if(p.EndsWith(".cs"))
			{
				scriptPathSet.Add(p);
			}
		}

		
		

		SortedDictionary<string, List<KeyValuePair<string, string>>> monoScriptInfoMap = new SortedDictionary<string, List<KeyValuePair<string, string>>>();

		foreach (var scriptPath in scriptPathSet)
		{
			MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath);
			if (script != null && script.GetClass() != null && (script.GetClass().IsSubclassOf(typeof(MonoBehaviour)) || script.GetClass().IsSubclassOf(typeof(ScriptableObject))))
			{
				MonoScriptDumper.DumpType(script.GetClass(), false, monoScriptInfoMap);
			}
			// else
			// {
			//     throw new System.Exception("load MonoScript failed: " + scriptPath);
			// }
		}

		return monoScriptInfoMap;
	}


	public static void SetGroupBuildAndLoadPathToRemote(bool setRemote)
	{
		if(setRemote)
		{
			UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.DisableCatalogUpdateOnStartup = true;
		}

		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.BuildRemoteCatalog = true;

		


		HashSet<string> assetPathSet = new HashSet<string>();
		List<AddressableAssetGroup> groups = AddressableAssetSettingsDefaultObject.Settings.groups;
		
		foreach(var group in groups)
		{
            if (group == null) continue;

            var schema = group.GetSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>();
			if(schema != null)
			{
				if(setRemote)
				{
					schema.BuildPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Remote", "BuildPath"));
					schema.LoadPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Remote", "LoadPath"));
					schema.UseAssetBundleCache = true;
				}
				else
				{
					schema.BuildPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Local", "BuildPath"));
					schema.LoadPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Local", "LoadPath"));
				}
			}

			var schema2 = group.GetSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema>();
			if(schema2 != null)
			{
				schema2.StaticContent = !setRemote;
			}
		}

		AssetDatabase.SaveAssets();
	}

	public static void BuildByCmd()
	{
		bool setRemotePath = false;
		int minAppVersion = 0;
        string outputPath = "output_path";
		string[] args = System.Environment.GetCommandLineArgs();


		for (int i = 0; i < args.Length; i++)
		{
			string arg = args[i];

			if (arg == "-output_path")
			{
                outputPath = args[++i];
			}
            else if (arg == "-min_app_version")
			{
                minAppVersion = int.Parse(args[++i]);
			}
			else if (arg == "-set_remote_path")
			{
                setRemotePath = bool.Parse(args[++i]);;
			}
		}

		Build(setRemotePath, minAppVersion, outputPath);
	}

	public static string GetKeyName(string prefix, string keyName)
	{
		var a = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.profileSettings.GetVariableNames();
		foreach(var e in a)
		{
			if(e.Contains(prefix) && e.Contains(keyName))
			{
				return e;
			}
		}

		return null;
	}

	public static void Build(bool setRemotePath, int minAppVersion, string outputPath)
	{
		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.activeProfileId = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.profileSettings.GetProfileId("Default");
		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogBuildPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Remote", "BuildPath"));
		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogLoadPath.SetVariableByName(AddressableAssetSettingsDefaultObject.Settings, GetKeyName("Remote", "LoadPath"));

		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.BuildAddressablesWithPlayerBuild = AddressableAssetSettings.PlayerBuildOption.DoNotBuildWithPlayer;

		SetGroupBuildAndLoadPathToRemote(setRemotePath);

		UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true, true);

		AssetDatabase.SaveAssets();
		
		Build();

		
		if(string.IsNullOrEmpty(outputPath))
		{
			return;
		}


		string addrssableOutputPath = outputPath + $"/{HotUpdateAddressableMgr.hotupdate_dir_name}";
		CommonUtil.Delete(addrssableOutputPath);
		CommonUtil.CreateDir(addrssableOutputPath);

		string versionOutputPath = outputPath + "/version_info";
		CommonUtil.CreateDir(versionOutputPath);

		string remoteBuildPath = GetRemoteBuildPath();

		if(!CommonUtil.Exists(remoteBuildPath))
		{
			return;
		}

		string[] filePaths = System.IO.Directory.GetFiles(remoteBuildPath, "*", System.IO.SearchOption.AllDirectories);
		foreach(var fp in filePaths)
		{
			var fn = System.IO.Path.GetFileName(fp);
			string destPath = addrssableOutputPath + "/" + fn;
			if(CommonUtil.Exists(destPath))
			{
				throw new System.Exception("file existed: " + destPath);
			}
			CommonUtil.Copy(fp, destPath);
		}

        var catalogHashPath = System.IO.Directory.GetFiles(addrssableOutputPath, "catalog*.hash", System.IO.SearchOption.TopDirectoryOnly)[0];
        var catalogPathWithoutExt = catalogHashPath.Substring(0, catalogHashPath.Length - 5);
        var catalogJsonPath = catalogPathWithoutExt + ".json";

        var md5 = MD5Hash.GetMD5HashFromFile(catalogJsonPath);
        var newCatalogHashPath = $"{catalogPathWithoutExt}_{md5}.hash";
        var newCatalogJsonPath = $"{catalogPathWithoutExt}_{md5}.json";

        CommonUtil.Move(catalogHashPath, newCatalogHashPath);
        CommonUtil.Move(catalogJsonPath, newCatalogJsonPath);

        string dir = System.IO.Path.GetDirectoryName(catalogHashPath);
        // CommonUtil.Copy(catalogHashPath, dir + "/catalog.hash");
        // CommonUtil.Copy(catalogJsonPath, dir + "/catalog.json");



        {
            HashSet<string> extSet = new HashSet<string>();
			HashSet<string> scriptFilePathSet = new HashSet<string>();
			
			string[] guids = AssetDatabase.FindAssets("t:script", new string[]{"Assets"});
			foreach(var guid in guids)
			{
				string assetPath = AssetDatabase.GUIDToAssetPath(guid);
				assetPath = assetPath.Replace("\\", "/");
				scriptFilePathSet.Add(assetPath);
			}

			SortedDictionary<string, List<KeyValuePair<string, string>>> monoScriptInfoMap = new SortedDictionary<string, List<KeyValuePair<string, string>>>();

			foreach (var scriptPath in scriptFilePathSet)
			{
				MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath);
				if (script != null && script.GetClass() != null && (script.GetClass().IsSubclassOf(typeof(MonoBehaviour)) || script.GetClass().IsSubclassOf(typeof(ScriptableObject))))
				{
					MonoScriptDumper.DumpType(script.GetClass(), false, monoScriptInfoMap);
				}
				// else
				// {
				//     throw new System.Exception("load MonoScript failed: " + scriptPath);
				// }
			}


			CommonUtil.ObjectToJsonFile(monoScriptInfoMap, versionOutputPath + "/" + AppManager.GetAppInfo().hotupdate_version + "_monoscript_info.txt");
		}
		

		if(minAppVersion == 0)
		{
			minAppVersion = AppManager.GetAppInfo().hotupdate_version;

			SortedDictionary<string, List<KeyValuePair<string, string>>> abScriptInfos = GetAddressableDependenceScriptInfos();

			SortedDictionary<int, string> versionPathMap = new SortedDictionary<int, string>();
			string[] paths = System.IO.Directory.GetFiles(versionOutputPath, "*_monoscript_info.txt", System.IO.SearchOption.TopDirectoryOnly);
			foreach(var p in paths)
			{
				string fileName = System.IO.Path.GetFileName(p);
				string[] infos = fileName.Split('_');
				int version = int.Parse(infos[0]);

				versionPathMap.Add(version, p);
			}

			foreach(var entry in versionPathMap.Reverse())
			{
				string str2 = CommonUtil.fileToString(entry.Value);
				var versionScriptInfos = JsonConvert.DeserializeObject<SortedDictionary<string, List<KeyValuePair<string, string>>>>(str2);
				if(ScriptInfoEqualsOrInclude(versionScriptInfos, abScriptInfos))
				{
					minAppVersion = entry.Key;
				}
				else
				{
					break;
				}
			}
		}
		else if(minAppVersion < 0)
		{
			minAppVersion = AppManager.GetAppInfo().hotupdate_version;
		}

		AddressableInfo info = new AddressableInfo();
		info.hotupdate_version = AppManager.GetAppInfo().hotupdate_version;
		info.min_app_hotupdate_version = minAppVersion;
		info.build_timestamp = CommonUtil.GetTimeStamp();
		System.DateTime time = System.DateTime.Now;
		info.build_time = time.ToString("u");

		string str = JsonConvert.SerializeObject(info, Formatting.Indented);


		CommonUtil.stringToFile(dir + "/" + addressableInfoFileName, str);
	}

	[MenuItem("cframework/AddressableTool/Build")]
	public static void Build()
	{
		string remoteBuildPath = AddressableAssetSettingsDefaultObject.Settings.RemoteCatalogBuildPath.GetValue(AddressableAssetSettingsDefaultObject.Settings);

		CommonUtil.Delete(remoteBuildPath);


		UnityEditor.AddressableAssets.Build.AddressablesPlayerBuildResult result;
		AddressableAssetSettings.BuildPlayerContent(out result);


		if(!string.IsNullOrEmpty(result.Error))
		{
			throw new System.Exception(result.Error);
		}
	}

	public static void CopyLocalBundleToStreamingAssetDir(int inAppBundleSize)
	{
		string remoteBuildPath = GetRemoteBuildPath();
		string destPath = Application.streamingAssetsPath + "/" + HotUpdateAddressableMgr.hotupdate_dir_name;

		if(CommonUtil.Exists(destPath))
		{
			CommonUtil.Delete(destPath);
		}
		CommonUtil.CreateDir(destPath);


		string[] paths = System.IO.Directory.GetFiles(remoteBuildPath, "*.bundle", System.IO.SearchOption.AllDirectories);

		System.Array.Sort(paths);

		List<string> inAppBundleNames = new List<string>();
		long sum = 0;
		for(int i = 0; i < paths.Length; i++)
		{
			string filePath = paths[i];
			System.IO.FileInfo file = new System.IO.FileInfo(filePath);
			if(inAppBundleSize < 0 || sum + file.Length <= inAppBundleSize * 1024 * 1024)
			{
				sum += file.Length;

				string fileName = System.IO.Path.GetFileName(filePath);
				CommonUtil.Copy(filePath, destPath + "/" + fileName);

				inAppBundleNames.Add(fileName);
			}
		}

		string str = JsonConvert.SerializeObject(inAppBundleNames, Formatting.Indented);
		CommonUtil.stringToFile(destPath + "/" + HotUpdateAddressableMgr.local_bundle_config_name, str);
	}

	public static void DisableAssetAddressable(string assetpath)
	{
		string guid = AssetDatabase.AssetPathToGUID(assetpath);
		List<AddressableAssetGroup> groups = AddressableAssetSettingsDefaultObject.Settings.groups;
		foreach(var group in groups)
		{
            if (group == null) continue;

            AddressableAssetEntry entry = group.GetAssetEntry(guid);
			if(entry != null)
			{
				group.RemoveAssetEntry(entry);
			}
		}
	}

	public static void EnableAssetAddressable(string assetpath)
	{
		string guid = AssetDatabase.AssetPathToGUID(assetpath);
		List<AddressableAssetGroup> groups = AddressableAssetSettingsDefaultObject.Settings.groups;
		foreach(var group in groups)
		{
            if (group == null) continue;

            AddressableAssetEntry entry = group.GetAssetEntry(guid);
			if(entry != null)
			{
				return;
			}
		}

		foreach(var group2 in AddressableAssetSettingsDefaultObject.Settings.groups)
		{
            if (group2 == null) continue;

            if (group2.GetSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>() != null)
			{
				var entry2 = AddressableAssetSettingsDefaultObject.Settings.CreateOrMoveEntry(guid, group2);

				AddressableAssetSettingsDefaultObject.Settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry2, true);
				AssetDatabase.SaveAssets();

				return;
			}
		}
	}
}